package com.gupaoedu.vip.netty.io.nio;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @program:gupaoedu-vip-netty-io
 * @description:同步非阻塞IO模型
 * NIO的操作过于繁琐，于是有了netty
 * Netty就是对这一系列繁琐的操作进行了封装
 * @author:Gongchao
 * @date:2020-04-27
 **/
public class NIOServer {

    private int port = 8080;

    //轮询器
    private Selector selector;

    //缓冲区
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    private NIOServer(int port) {

        this.port = port;
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(this.port));
            //BIO升级NIO，为了兼容BIO，NIO模型默认采用阻塞方式
            serverSocketChannel.configureBlocking(false);

            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void listen() {
        System.out.println("listen on "+this.port+".");
        while (true) {
            try {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
                //不断地迭代，轮询
                //同步体现在这，每次只能拿一个key，每次只能处理一个状态
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    //每一个key代表一个状态，对应一种业务操作
                    //数据就绪，数据可读，数据可写 等等
                    process(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /* *
     *
     * 具体的业务方法，每一次轮询，调用一次process，做一件是事情
     * 同步体现：在一个时间点，只能做一件事
     * @param:[key]
     * @return:void
     * @author:Gongchao
     * @date:2020-四月-27
     *
     */
    private void process(SelectionKey key) throws IOException {
        if(key.isAcceptable()) {
            //针对每一种状态给出一个反应
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            //这个方法体现非阻塞，不管有没有准备，必须给出状态和反馈
            SocketChannel socketChannel = server.accept();
            //设置为非阻塞
            socketChannel.configureBlocking(false);
            //当数据准备就绪的时候，将状态改为可读
            key = socketChannel.register(selector,SelectionKey.OP_READ);
        }else if(key.isReadable()) {
            //key.channel 从多路复用器中拿到客户端的引用
            SocketChannel socketChannel = (SocketChannel) key.channel();
            int len = socketChannel.read(buffer);
            if(len > 0) {
                buffer.flip();
//                String content = new String(buffer.array(),0,len);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(buffer.array(),0,len);
                String content = byteArrayOutputStream.toString("UTF-8");
                key = socketChannel.register(selector,SelectionKey.OP_WRITE);
                //key上面带一个附件，稍候将数据写出去
                key.attach(content);
                System.out.println("读取内容："+content);
            }
        }else if(key.isWritable()) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            String content = String.valueOf(key.attachment());
            socketChannel.write(ByteBuffer.wrap(("输出：" + content).getBytes()));
            socketChannel.close();
        }
    }

    public static void main(String[] args) {
        new NIOServer(8080).listen();
    }
}
